/**
 * @file memory.c
 *       carl_pan (1586975697@qq.com)
 * @brief 
 * @version 0.1
 * @date 2023-04-03
 * 
 * @copyright Copyright (c) 2023
 * 
 */
#include "malloc.h"
// #include <driver/abstract/log/log.h>
/**
 * @brief Supports safe operation in multithreaded environments
 *
 */

#define THREAD_PROTECT      0
#define MEMORY_DEUBG        0


#ifndef THREAD_PROTECT
#define THREAD_PROTECT      1
#endif /* THREAD_PROTECT */

#if (THREAD_PROTECT)
#include <FreeRTOS.h>
#include <queue.h>
#include <semphr.h>
#endif

#ifndef MEMORY_DEUBG
#define MEMORY_DEUBG        1
#endif

#if (MEMORY_DEUBG)
    #define memoryINFO(...) do { \
        rawPrint("memory :_INFOR_"); \
        rawPrint(__VA_ARGS__); \
    } while(0)

    #define memoryASSERT(...) do { \
        rawPrint("memory :_ERROR_ failed at line [ %d ] in [ %s ] :", __LINE__, __FILE__); \
        rawPrint(__VA_ARGS__); \
    } while(0)
#else
    #define memoryASSERT(...)
    #define memoryINFO(...)
#endif


#ifndef NULL
#define NULL 0
#endif 

/*------------------------------------------Data and structure-------------------------------------*/
/* Fill in the following data based on chip and memory conditions */
/*------------------------------------------POOL SRAMIN----------------------------------------*/
#define INVALID_OFFSET (0xFFFFFFFF)
#define SRAMBANK  POOL_NUM

/* SRAMIN : in RAM */
#define SRAMIN_BLOCK_SIZE	    (32)  		                         /* mini block size */
#define SRAMIN_MAX_SIZE		    (32 * 1024) 	                     /* memory pool size */
#define SRAMIN_ALLOC_TABLE_SIZE (SRAMIN_MAX_SIZE/SRAMIN_BLOCK_SIZE)  /* block mamagers size */
/* memory check */
#if (SRAMIN_MAX_SIZE / SRAMIN_BLOCK_SIZE +  SRAMIN_MAX_SIZE * 2 > 128 * 1024)
#error BUILD_OUTPUT : memory [SRAMIN] over flow.
#endif/* memory check */
/*------------------------------------------POOL SRAMIN END------------------------------------*/

/*------------------------------------------POOL CCMRAM----------------------------------------*/
/* CCMRAM in STM32F407ZG : 64K. Only CPU can access it */
#define CCMRAM_BLOCK_SIZE	    (32)  		                         /* mini block size */
#define CCMRAM_MAX_SIZE		    (60 * 1024)                          /* memory pool size */
#define CCMRAM_ALLOC_TABLE_SIZE (CCMRAM_MAX_SIZE/CCMRAM_BLOCK_SIZE)  /* block mamagers size */
/* memory check */
#if (CCMRAM_MAX_SIZE / CCMRAM_BLOCK_SIZE +  CCMRAM_ALLOC_TABLE_SIZE * 2 > 64 * 1024)
#error BUILD_OUTPUT : memory [SRAMCCM] over flow.
#endif/* memory check */
/*------------------------------------------POOL CCMRAM END------------------------------------*/

/* center structure */
struct _m_mallco_dev
{
    void (*init)(uint8_t, const char *); //memory pool init
	uint8_t (*usage)(uint8_t); 		//get pool usage
	uint8_t *membase[SRAMBANK];     //memory pools
	uint16_t *memmamager[SRAMBANK]; //memory mamagers
	uint8_t memrdy[SRAMBANK];       //ready flag
    uint16_t freeBlockNum[SRAMBANK];//free block num

#if (THREAD_PROTECT)
    SemaphoreHandle_t mutex[SRAMBANK];  //create mutex for each pool
#endif
};

/* memory pools buff */
__align(4) uint8_t sraminBase[SRAMIN_MAX_SIZE];
__align(4) uint8_t ccmramBase[CCMRAM_MAX_SIZE] __attribute__((at(0x10000000)));

/* memory mamagers buff*/
uint16_t sraminMamagerBase[SRAMIN_ALLOC_TABLE_SIZE];
uint16_t ccmramMamagerBase[CCMRAM_ALLOC_TABLE_SIZE] __attribute__((at(0x10000000 + CCMRAM_MAX_SIZE)));

/* memory pools parameters */
const uint32_t memblksize[SRAMBANK] = {SRAMIN_BLOCK_SIZE, CCMRAM_BLOCK_SIZE};
const uint32_t memsize[SRAMBANK] = {SRAMIN_MAX_SIZE, CCMRAM_MAX_SIZE};
const uint32_t memtblsize[SRAMBANK] = {SRAMIN_ALLOC_TABLE_SIZE, CCMRAM_ALLOC_TABLE_SIZE};

struct _m_mallco_dev mallco_dev = \
{ \
	memInit,  \
	mymemUsage, \
	{sraminBase, ccmramBase}, \
	{sraminMamagerBase, ccmramMamagerBase}, \
	{0, 0}, \
    {SRAMIN_ALLOC_TABLE_SIZE, CCMRAM_ALLOC_TABLE_SIZE},
#if (THREAD_PROTECT)
    {NULL, NULL}
#endif
};
/*------------------------------------------Data and structure end---------------------------------*/
/**
 * @brief copy data to destination(No Input Check)
 * 
 * @param des destination address
 * @param src source address
 * @param lenth
 */
static void mymemCopy(void *des, void *src, uint32_t lenth)
{
    if (lenth == 0 || des == NULL || src == NULL)
    {
        /* wrong input */
        return;
    }

    uint8_t *xdes = des;
	uint8_t *xsrc = src;
	while(lenth--) *xdes++ = *xsrc++;
}

/**
 * @brief mymemset (No Input Check)
 * 
 * @param buff 
 * @param data 
 * @param count 
 */
static void mymemSet(void *buff, uint8_t data, uint32_t count)
{
    if (buff == NULL || count == 0)
    {
        return;
    }
	uint8_t *xs = buff;
	while(count--) *xs++ = data;
}


/**
 * @brief mymemMalloc
 * 
 * @param memx 
 * @param size 
 * @return uint32_t memory offset
 */
static uint32_t mymemMalloc(uint8_t memx, uint32_t size)  
{
    if(size == 0)
    {
        return INVALID_OFFSET; /* Can not return NULL, NULL is valid offset */
    }

    uint32_t i;
    uint32_t offset = 0;  
    uint16_t blockNeed;
    uint16_t blockFree = 0;
    if(!mallco_dev.memrdy[memx])
    {
        mallco_dev.init(memx, "Auto initialized a pool...");//check pool init flag
    }

    /* calculate block number in need */
    blockNeed = size / memblksize[memx];
    if (size % memblksize[memx])
    {
        blockNeed ++;
    }

    /* find contiguous block */
    for (offset = memtblsize[memx] - 1; offset != 0; offset --)
    {
		if ( !mallco_dev.memmamager[memx][offset] )
        {
            blockFree ++;
        }
		else 
        {
            blockFree = 0;
        }

		if(blockFree == blockNeed)
		{
            for(i = 0; i < blockNeed; i ++)
            {  
                mallco_dev.memmamager[memx][offset + i] = blockNeed;  
            }
            mallco_dev.freeBlockNum[memx] -= blockNeed;
            return (offset * memblksize[memx]);
		}
    }  
    return INVALID_OFFSET;
}  

/**
 * @brief mymemFree
 * 
 * @param memx 
 * @param offset 
 * @return uint8_t 
 */
static uint8_t mymemFree(uint8_t memx, uint32_t offset)  
{  
    uint32_t i; 
    if (!mallco_dev.memrdy[memx])
    {
        mallco_dev.init(memx, "Auto initialized a pool...");    
        return 1;
    }  
    if (offset < memsize[memx]) 
    {  
		uint32_t index = offset / memblksize[memx];  
        uint32_t blockNeed = mallco_dev.memmamager[memx][index];
        for (i = 0; i < blockNeed; i ++)
        {
            mallco_dev.memmamager[memx][index + i] = 0;
        }
        mallco_dev.freeBlockNum[memx] += blockNeed;
        return 0;
    }
    else
    {
        return 2;/* overflow */ 
    }
}
/*------------------------------------------API----------------------------------------------*/
/**
 * @brief myMalloc
 * 
 * @param memx 
 * @param size 
 * @return void* 
 */
void *myMalloc(uint8_t memx, uint32_t size)  
{
    if (memx >= POOL_NUM)
    {
        memoryASSERT(" not find memory memx !"LINE_END);
        return NULL;  
    }

    if (size == 0)
    {
        memoryASSERT(" Attempting to obtain 0 bytes of space !"LINE_END);
        return NULL;
    }

#if (THREAD_PROTECT)
    if (mallco_dev.mutex[memx] != NULL)
    {
        if (pdFALSE == xSemaphoreTake(mallco_dev.mutex[memx], portMAX_DELAY))
        {
            memoryASSERT(" xSemaphoreTake failed !"LINE_END);            
            return NULL;
        }
    }
    else
    {
        memoryASSERT(" mutex not initialized !"LINE_END);
        return NULL;
    }
#endif
    void *p = NULL;
    uint32_t offset = mymemMalloc(memx, size);

    if (offset != INVALID_OFFSET)
    {
        p = (void*)((uint32_t)mallco_dev.membase[memx] + offset);
    }
    else
    {
        memoryASSERT(" malloc failed"LINE_END);
    }

#if (THREAD_PROTECT)
    if (mallco_dev.mutex[memx] != NULL)
    {
        if (pdFALSE == xSemaphoreGive(mallco_dev.mutex[memx]))
        {
            memoryASSERT(" xSemaphoreGive failed"LINE_END);
        }
    }
#endif
    return p;
}

/**
 * @brief myFree
 * 
 * @param memx 
 * @param ptr 
 */
void myFree(uint8_t memx,void *ptr)
{
    if (memx >= POOL_NUM)
    {
        memoryASSERT(" not find memory memx !"LINE_END);
        return;  
    }
#if (THREAD_PROTECT)
    if (mallco_dev.mutex[memx] != NULL)
    {
        if (pdFALSE == xSemaphoreTake(mallco_dev.mutex[memx], portMAX_DELAY))
        {
            memoryASSERT(" xSemaphoreTake failed !"LINE_END);            
            return ;
        }
    }
    else
    {
        memoryASSERT(" mutex not initialized !"LINE_END);
        return ;
    }
#endif

    uint32_t offset;  
    if ((uint32_t)ptr < (uint32_t)mallco_dev.membase[memx] || (uint32_t)ptr > (uint32_t)mallco_dev.membase[memx] + (uint32_t)memsize[memx])
    {
        return ;/* wrong input */
    }
    offset = (uint32_t)ptr - (uint32_t)mallco_dev.membase[memx];  
    mymemFree(memx, offset);

#if (THREAD_PROTECT)
    if (mallco_dev.mutex[memx] != NULL)
    {
        if (pdFALSE == xSemaphoreGive(mallco_dev.mutex[memx]))
        {
            memoryASSERT(" xSemaphoreGive failed"LINE_END);
        }
    }
#endif
}

/**
 * @brief myRealloc
 * 
 * @param memx 
 * @param ptr 
 * @param size 
 * @return void* 
 */
void *myRealloc(uint8_t memx, void *ptr, uint32_t size)
{
    if (memx >= POOL_NUM)
    {
        memoryASSERT(" not find memory memx !"LINE_END);
        return NULL;  
    }
#if (THREAD_PROTECT)
    if (mallco_dev.mutex[memx] != NULL)
    {
        if (pdFALSE == xSemaphoreTake(mallco_dev.mutex[memx], portMAX_DELAY))
        {
            memoryASSERT(" xSemaphoreTake failed !"LINE_END);
            return NULL;
        }
    }
    else
    {
        memoryASSERT(" mutex not initialized !"LINE_END);
        return NULL;
    }
#endif

    void *p = NULL;
    uint32_t offset = mymemMalloc(memx, size);
    if (offset != INVALID_OFFSET)
    {
	    mymemCopy((void*)((uint32_t)mallco_dev.membase[memx] + offset), ptr, size);
        myFree(memx, ptr);
        p = (void*)((uint32_t)mallco_dev.membase[memx] + offset);
    }
    else
    {
        memoryASSERT(" malloc failed"LINE_END);
    }

#if (THREAD_PROTECT)
    if (mallco_dev.mutex[memx] != NULL)
    {
        if (pdFALSE == xSemaphoreGive(mallco_dev.mutex[memx]))
        {
            memoryASSERT(" xSemaphoreGive failed"LINE_END);
        }
    }
#endif
    return p;
}

/**
 * @brief mem init
 * 
 * @param memx 
 */
void memInit(uint8_t memx, const char *poolName)
{
    if (memx >= POOL_NUM)
    {
        memoryASSERT(" not find memory memx !"LINE_END);
        return;  
    }

    memoryINFO(" [ %s ] init ..."LINE_END, poolName);
    if (memx >= POOL_NUM)
    {
        memoryASSERT(" memory pool wrong input !"LINE_END);
    }
#if (THREAD_PROTECT)
    if (mallco_dev.mutex[memx] == NULL)
    {
        mallco_dev.mutex[memx] = xSemaphoreCreateMutex();
    }
    else
    {
        memoryASSERT(" memory pool reinit !"LINE_END);
    }

    if (mallco_dev.mutex[memx] == NULL)
    {
        memoryASSERT(" xSemaphoreCreateMutex failed !"LINE_END);
    }
    else
    {
        if (pdFALSE == xSemaphoreTake(mallco_dev.mutex[memx], portMAX_DELAY))
        {
            memoryASSERT(" xSemaphoreTake failed !"LINE_END);
        }
    }
#endif
    if (mallco_dev.memrdy[memx] == 0)
    {
        mymemSet(mallco_dev.membase[memx], 0, memsize[memx]); /* init memory pools */
        mymemSet(mallco_dev.memmamager[memx], 0, memtblsize[memx] * (sizeof(uint16_t) / (sizeof(uint8_t)))); /* init memory mamagers */
        mallco_dev.memrdy[memx] = 1;
    }
    else
    {
        memoryASSERT(" memory pool reinit !"LINE_END);
    }

#if (THREAD_PROTECT)
    if (mallco_dev.mutex[memx] != NULL)
    {
        if (pdFALSE == xSemaphoreGive(mallco_dev.mutex[memx]))
        {
            memoryASSERT(" xSemaphoreGive failed !"LINE_END);
        }
    }
#endif
}

/**
 * @brief get one pool memory usage
 * 
 * @param memx 
 * @return uint8_t %
 */
uint8_t mymemUsage(uint8_t memx)
{
    if (memx >= POOL_NUM)
    {
        memoryASSERT(" not find memory memx !"LINE_END);
        return 0;  
    }

    return (mallco_dev.freeBlockNum[memx] * 100 / (memtblsize[memx]));
}

//end






